
#include <fcntl.h>
#include <getopt.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <algorithm>
#include <list>
#include <string>

#include "../tools/socket.h"
#include "../tools/logging.h"


int usage(const char * argv0)
{
    printf("Usage: %s [OPTION]... COMMAND\n", argv0);
    printf("\t-h, --help            display this help and exit.\n");
    printf("\t-p, --port PORT       set listening port.\n");
    printf("\t-i, --ip IPADDR       set listening address.\n");
    printf("\t-d, --daemon          run as daemon.\n");
    return 1;
}

void mkdaemon()
{
    int pid = fork();
    if (pid < 0) {
        perror("fork1");
        exit(1);
    }
    if (pid > 0)
        exit(0);

    setsid();

    pid = fork();
    if (pid < 0) {
        perror("fork2");
        exit(1);
    }
    if (pid > 0)
        exit(0);
    umask(0);
}

int main(int argc, char * argv[])
{
    std::string ip("0.0.0.0");
    int         port = 10234;
    bool        daemon = false;

    struct option long_options[] = {
        {"port", required_argument, NULL, 'p'},
        {"ip",   required_argument, NULL, 'i'},
        {"daemon", no_argument,     NULL, 'd'},
        {"help", no_argument,       NULL, 'h'},
        {NULL, 0, NULL, 0},
    };
    int option_index = 0;
    int opt;
    while ((opt = getopt_long(argc, argv, "+p:hi:d", long_options, &option_index)) != -1) {
        switch (opt) {
        case 'h':
            return usage(argv[0]);
            break;
        case 'p':
            port = atoi(optarg);
            break;
        case 'i':
            ip = optarg;
            break;
        case 'd':
            daemon = true;
            break;
        default:
            printf("default. opt = %d\n", opt);
            break;
        }
        if (opt == '?')
            break;
    }

    if (optind >= argc) {
        return usage(argv[0]);
    }

    // printf("argc = %d, optind = %d, daemon = %d\n", argc, optind, daemon);
    // return 0;

    int count = argc - optind + 1;
    char **args = new char*[count];
    for (int i = 0; i < count - 1; i++) {
        args[i] = strdup(argv[optind + i]);
    }
    args[count - 1] = NULL;

    // for (char ** p = args; *p != NULL; p++) {
    //     printf("%s\n", *p);
    // }
    // return 0;

    tools::CSocket  sockL;
    std::list<tools::CSocket*>  socks;
    socks.clear();

    sockL.Create();
    sockL.Bind(port);
    sockL.SetHostIP(ip.c_str());
    sockL.Listen();

    int pipe_p_2_c[2];
    int pipe_c_2_p[2];
    int ret = pipe(pipe_p_2_c);
    if (ret != 0) {
        perror("pipe");
        return -1;
    }
    ret = pipe(pipe_c_2_p);
     if (ret != 0) {
        perror("pipe");
        return -1;
    }

    if (daemon) {
        mkdaemon();
    }

    int pid = fork();
    if (pid == 0) {
        close(STDOUT_FILENO);
        close(STDIN_FILENO);

        int ret = dup2(pipe_c_2_p[1], STDOUT_FILENO);
        fprintf(stdout, "dup out ret = %d\n", ret);
        ret = dup2(pipe_p_2_c[0], STDIN_FILENO);
        fprintf(stdout, "dup in ret = %d\n", ret);

        close(pipe_c_2_p[0]);
        close(pipe_c_2_p[1]);
        close(pipe_p_2_c[0]);
        close(pipe_p_2_c[1]);

        execvp(args[0], args);
        perror("execlp");
    }
    // printf("fork pid = %d\n", pid);

    FILE* logfp = stdout;
    if (daemon) {
        close(STDIN_FILENO);
        logfp = fopen("mkd.out", "w+");
        close(STDOUT_FILENO);
        close(STDERR_FILENO);
    }
    setbuf(logfp, NULL);

    close(pipe_c_2_p[1]);
    close(pipe_p_2_c[0]);

    while (true) {
        struct timeval tv;
        tv.tv_sec = 1;
        tv.tv_usec = 0;

        fd_set rfds;
        FD_ZERO(&rfds);
        if (!daemon) {
            FD_SET(STDIN_FILENO, &rfds);
        }
        FD_SET(pipe_c_2_p[0], &rfds);
        FD_SET(sockL.GetSocket(), &rfds);

        int maxfd = -1;
        if (daemon) {
            maxfd = std::max(pipe_c_2_p[0], sockL.GetSocket());
        } else {
            maxfd = std::max(std::max(STDIN_FILENO, pipe_c_2_p[0]), sockL.GetSocket());
        }
        if (!socks.empty()) {
            for (std::list<tools::CSocket*>::iterator iter = socks.begin(); iter != socks.end(); iter++) {
                int fd = (*iter)->GetSocket();
                if (fd > maxfd)
                    maxfd = fd;
                FD_SET(fd, &rfds);
            }
        }

        int ret = select(maxfd + 1, &rfds, NULL, NULL, &tv);
        if (ret < 0) {
            fprintf(logfp, "<ERROR> select: %s\n", strerror(errno));
            break;
        }
        if (ret == 0) {
            continue;
        }

        char buffer[1024];

        if (!daemon) {
            if (FD_ISSET(STDIN_FILENO, &rfds)) {
                if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
                    write(pipe_p_2_c[1], buffer, strlen(buffer));
                }
            }
        }
        if (FD_ISSET(pipe_c_2_p[0], &rfds)) {
            int bytes = read(pipe_c_2_p[0], buffer, 1023);
            buffer[bytes] = 0;
            if (bytes == 0) {
                fprintf(logfp, "<DEBUG> read 0 bytes\n");
                break;
            } else {
                fprintf(logfp, "%s", buffer);
                fflush(logfp);
                // if (buffer[strlen(buffer) - 1] == '\n')
                //     fprintf(logfp, "[read %04d bytes] %s", bytes, buffer);
                // else
                //     fprintf(logfp, "[read %04d bytes] %s\n", bytes, buffer);
                if (!socks.empty()) {
                    for (std::list<tools::CSocket*>::iterator iter = socks.begin(); iter != socks.end(); iter++) {
                        (*iter)->Send(buffer, bytes);
                    }
                }
            }
        }
        if (!socks.empty()) {
            for (std::list<tools::CSocket*>::iterator iter = socks.begin(); iter != socks.end(); /* */) {
                int fd = (*iter)->GetSocket();
                if (FD_ISSET(fd, &rfds)) {
                    int bytes = read(fd, buffer, 1023);
                    if (bytes > 0) {
                        buffer[bytes] = 0;
                        fprintf(logfp, "<DEBUG> read %d bytes from network: %s\n", bytes, buffer);
                        write(pipe_p_2_c[1], buffer, bytes);
                    } else {
                        fprintf(logfp, "<INFO> client %s:%d disconnected\n", (*iter)->GetRemoteIP().c_str(), (*iter)->GetRemotePort());
                        iter = socks.erase(iter);
                        continue;
                    }
                }
                iter++;
            }
        }
        if (FD_ISSET(sockL.GetSocket(), &rfds)) {
            tools::CSocket* s = new tools::CSocket();
            sockL.Accept(*s);
            socks.push_back(s);
            printf("<INFO> client %s:%d connected\n", s->GetRemoteIP().c_str(), s->GetRemotePort());
        }
    }
    return 0;
}


